﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory;
using System.IO;
using Genuilder.Extensibility;


namespace Genuilder.Extensions.XamlVerifier
{

	public abstract class XamlVerifierError
	{
		public string Suggestion
		{
			get;
			set;
		}

		public string File
		{
			get;
			set;
		}

		public Genuilder.Extensibility.Location Location
		{
			get;
			set;
		}

		public abstract string CurrentValue
		{
			get;
		}

		public int Index
		{
			get;
			set;
		}
	}

	public class XamlVerifierPropertyError : XamlVerifierError
	{
		public string PropertyName
		{
			get;
			set;
		}

		public string ClassName
		{
			get;
			set;
		}

		public override string CurrentValue
		{
			get
			{
				return PropertyName;
			}
		}
	}

	public class XamlVerifierTypeError : XamlVerifierError
	{
		public string TypeName
		{
			get;
			set;
		}

		public override string CurrentValue
		{
			get
			{
				return TypeName;
			}
		}
	}

	public class CodeFileCollection : List<CompilationUnit>
	{
		public void Add(string file)
		{
			var parser = ParserFactory.CreateParser(file);
			parser.ParseMethodBodies = false;
			parser.Parse();
			if(parser.Errors != null && parser.Errors.Count > 0)
				return;
			Add(parser.CompilationUnit);
		}

	}

	internal class TypeResolver : ICSharpCode.NRefactory.Visitors.AbstractAstVisitor
	{
		internal class ResolveResult
		{
			public ResolveResult()
			{
				Success = true;
			}
			public bool Success
			{
				get;
				set;
			}
			public string TypeName
			{
				get;
				set;
			}
			public String Suggestion
			{
				get;
				set;
			}
		}
		private class PropertyDecl
		{
			public PropertyDecl(string propertyName)
			{
				Name = propertyName;
			}

			public PropertyDecl(PropertyDeclaration propertyDeclaration)
				: this(propertyDeclaration.Name)
			{

				_Declaration = propertyDeclaration;
			}

			private readonly PropertyDeclaration _Declaration;
			public PropertyDeclaration Declaration
			{
				get
				{
					return _Declaration;
				}
			}

			public string Name
			{
				get;
				set;
			}
		}
		private class TypeDecl
		{
			public TypeDecl(string fullName)
				: this(ExtractName(fullName), ExtractNamespace(fullName))
			{
			}

			private static string ExtractNamespace(string fullName)
			{
				var splitted = fullName.Split('.');
				return String.Join(".", splitted.Take(splitted.Length - 1).ToArray());
			}

			private static string ExtractName(string fullName)
			{
				var splitted = fullName.Split('.');
				return splitted[splitted.Length - 1];
			}
			public TypeDecl(string name, string ns)
			{
				Namespace = ns;
				Name = name;
				_Properties = new FuzzyCollection<PropertyDecl>((a, b) => Metrics.LevenshteinDistance(a.Name, b.Name));
			}



			public string BaseType
			{
				get;
				set;
			}


			FuzzyCollection<PropertyDecl> _Properties;
			public FuzzyCollection<PropertyDecl> Properties
			{
				get
				{
					return _Properties;
				}
			}

			public string Name
			{
				get;
				set;
			}

			public string Namespace
			{
				get;
				set;
			}

			public override string ToString()
			{
				return FullName;
			}

			public string FullName
			{
				get
				{
					if(String.IsNullOrEmpty(Namespace))
						return Name;
					return Namespace + "." + Name;
				}
			}
		}



		public TypeResolver()
		{
			types = new FuzzyCollection<TypeDecl>((a, b) =>
			{
				return Math.Min(Metrics.LevenshteinDistance(a.Name, b.Name), Metrics.LevenshteinDistance(a.FullName, b.FullName));
			});
		}


		FuzzyCollection<TypeDecl> types;
		Stack<TypeDecl> stackTypes = new Stack<TypeDecl>();
		Stack<NamespaceDeclaration> nsStack = new Stack<NamespaceDeclaration>();

		public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data)
		{
			nsStack.Push(namespaceDeclaration);
			try
			{
				return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
			}
			finally
			{
				nsStack.Pop();
			}
		}

		Dictionary<Tuple<string, string>, TypeDecl> _TypeDecls = new Dictionary<Tuple<string, string>, TypeDecl>();
		public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
		{
			TypeDecl typeDecl = GetOrCreateTypeDecl(typeDeclaration);
			stackTypes.Push(typeDecl);
			try
			{
				return base.VisitTypeDeclaration(typeDeclaration, data);
			}
			finally
			{
				stackTypes.Pop();
			}
		}

		public override object VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data)
		{
			if(stackTypes.Count == 0)
				return null;
			var typeDecl = stackTypes.Peek();
			typeDecl.Properties.Add(new PropertyDecl(propertyDeclaration));
			return null;
		}

		private TypeDecl GetOrCreateTypeDecl(TypeDeclaration typeDeclaration)
		{
			var key = Tuple.Create(typeDeclaration.Name, CurrentNamespace ?? "");
			TypeDecl typeDecl = null;
			var baseType = typeDeclaration.BaseTypes.FirstOrDefault();

			if(!_TypeDecls.TryGetValue(key, out typeDecl))
			{
				typeDecl = new TypeDecl(typeDeclaration.Name, CurrentNamespace);
				types.Add(typeDecl);
				_TypeDecls.Add(key, typeDecl);
			}
			typeDecl.BaseType = baseType == null ? typeDecl.BaseType : baseType.ToString();
			return typeDecl;
		}



		FuzzyResult<PropertyDecl> GetProperty(string typeName, string propertyName, bool exact = false)
		{
			var type = types.GetClosest(new TypeDecl(typeName)).FirstOrDefault();
			if(type == null || (exact && type.Distance != 0.0))
				return null;
			var property = type.Value.Properties.GetClosest(new PropertyDecl(propertyName)).FirstOrDefault();

			if(property == null && type.Value.BaseType != null)
			{
				return GetProperty(type.Value.BaseType, propertyName, true);
			}
			return property;
		}

		internal ResolveResult FindProperty(string typeName, string propertyName)
		{
			var property = GetProperty(typeName, propertyName);

			if(property == null)
			{
				return new ResolveResult()
				{
					Success = false
				};
			}
			else
			{
				var tn = property.Value.Declaration.TypeReference.ToString();
				if(property.Distance == 0)
					return new ResolveResult()
					{
						TypeName = tn
					};
				else
					return new ResolveResult()
					{
						Success = false,
						Suggestion = property.Value.Name,
						TypeName = tn
					};
			}
		}

		internal ResolveResult FindType(string typeName)
		{
			var type = types.GetClosest(new TypeDecl(typeName)).FirstOrDefault();
			if(type == null)
				return new ResolveResult()
				{
					Success = false
				};
			if(type.Distance == 0)
				return new ResolveResult();

			var closest = new FuzzyCollection<string>(Metrics.LevenshteinDistance);
			closest.Add(type.Value.Name);
			closest.Add(type.Value.FullName);
			var c = closest.GetClosest(typeName).First();

			return new ResolveResult()
			{
				Success = false,
				Suggestion = c.Value
			};
		}

		public string CurrentNamespace
		{
			get
			{
				if(nsStack.Count == 0)
					return null;
				return String.Join(".", nsStack.Select(n => n.Name).ToArray());
			}
		}
	}
	internal class XamlVerifierContext
	{
		Stack<string> _Types = new Stack<string>();
		public Stack<String> Types
		{
			get
			{
				return _Types;
			}
		}
	}

	public class XamlVerifierEvaluator
	{
		private class StaticEnumerable<T> : IEnumerable<T>
		{
			IEnumerator<T> enumerator;
			public StaticEnumerable(IEnumerator<T> enumerator)
			{
				this.enumerator = enumerator;
			}
			#region IEnumerable<XamlVerifierError> Members

			public IEnumerator<T> GetEnumerator()
			{
				return enumerator;
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return enumerator;
			}

			#endregion
		}

		private readonly List<String> _XamlFiles = new List<string>();
		public List<String> XamlFiles
		{
			get
			{
				return _XamlFiles;
			}
		}
		private readonly CodeFileCollection _CodeFiles = new CodeFileCollection();
		public CodeFileCollection CodeFiles
		{
			get
			{
				return _CodeFiles;
			}
		}


		public IEnumerable<XamlVerifierError> AllErrors()
		{
			if(_AllErrors != null)
				return new StaticEnumerable<XamlVerifierError>(_AllErrors);
			else
			{
				_AllErrors = _AllErrorsCore().GetEnumerator();
				return new StaticEnumerable<XamlVerifierError>(_AllErrors);
			}
		}
		IEnumerator<XamlVerifierError> _AllErrors;


		private IEnumerable<XamlVerifierError> _AllErrorsCore()
		{
			TypeResolver resolver = new TypeResolver();
			foreach(var codeFile in CodeFiles)
			{
				resolver.VisitCompilationUnit(codeFile, null);
			}

			foreach(var xamlFile in XamlFiles)
			{
				XamlVerifierReader reader = new XamlVerifierReader(File.ReadAllText(xamlFile));
				XamlVerifierContext context = new XamlVerifierContext();
				foreach(var node in reader.ReadAll())
				{
					IEnumerable<XamlVerifierError> errors = node.Visit(resolver, context);
					if(errors != null)
					{
						foreach(var error in errors)
						{
							error.File = xamlFile;
							yield return error;
						}
					}
				}
			}
		}



		public XamlVerifierError NextError()
		{
			var errors = AllErrors().GetEnumerator();
			if(errors.MoveNext())
			{
				return errors.Current;
			}
			return null;
		}
	}
}
